﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace NGinnBPM.Rules.TableDriven
{
    

    /// <summary>
    /// hdr: ${VAL} - insert value, ${QVAL} - insert value in quotes
    /// value: literal or [list, of, values] or ![any, other, than, values]
    /// </summary>
    public class TableRulesetBuilder
    {
        


        private string SubstValue(string expr, string val, bool action)
        {
            string v = val.Trim();
            string quotedVal = string.Format("\"{0}\"", v);
            if (expr.IndexOf("${VAL}") >= 0 || expr.IndexOf("${QVAL}") >= 0)
            {
                v = expr.Replace("${VAL}", v);
                v = v.Replace("${QVAL}", quotedVal);
                return v;
            }
            else
                return string.Format(action ? "{0} = {1}" : "{0} == {1}", expr, v);
        }

        private string BuildBooExpression(string pattern, string cellValue, bool isAction)
        {
            cellValue = cellValue.Trim();
            if (cellValue.StartsWith("[") || cellValue.StartsWith("!["))
            {
                if (!cellValue.EndsWith("]"))
                    throw new Exception("Cell value should end with ']'");
                string innerv = cellValue.StartsWith("![") ? cellValue.Substring(2, cellValue.Length - 3) : cellValue.Substring(1, cellValue.Length - 2);
                string[] vals = innerv.Split(',');
                StringBuilder sb = new StringBuilder();
                foreach (string val in vals)
                {
                    if (sb.Length > 0) sb.Append(" or ");
                    sb.Append("(" + SubstValue(pattern, val, isAction) + ")");
                }
                return sb.ToString();
            }
            else
            {
                return SubstValue(pattern, cellValue, isAction);
            }
        }

        /// <summary>
        /// Load a rule table
        /// </summary>
        /// <param name="cr"></param>
        /// <returns></returns>
        public RuleTable ReadCsv(CsvReader cr)
        {
            RuleTable rt = new RuleTable();
            if (!cr.Read())
                throw new Exception("Header row missing");
            for (int i = 0; i < cr.CurrentRow.Count; i++)
            {
                rt.Columns.Add(new RuleTable.Column { Header = cr.CurrentRow[i] });
            }
            if (!cr.Read())
                throw new Exception("Second row missing (rule header definition)");
            for (int i = 0; i < cr.CurrentRow.Count; i++)
            {
                if (i == rt.Columns.Count)
                    rt.Columns.Add(new RuleTable.Column { Header = cr.CurrentRow[i] });
                rt.Columns[i].Expression = cr.CurrentRow[i];
            }

            while (cr.Read())
            {
                List<string> row = new List<string>(cr.CurrentRow);
                if (row.Count > rt.Columns.Count)
                    row.RemoveRange(rt.Columns.Count, row.Count - rt.Columns.Count);
                
                if (row.Count < rt.Columns.Count)
                    throw new Exception("Some fields are missing in row " + cr.LineNumber);

                rt.Rows.Add(new RuleTable.Row(cr.LineNumber, row));
            }
            return rt;
        }

        public RuleTable ReadCsv(string fileName, Encoding enc)
        {
            using (StreamReader sr = new StreamReader(fileName, enc))
            {
                CsvReader cr = new CsvReader(sr);
                return ReadCsv(cr);
            }
        }

        
             

        public RulesetDef BuildRuleset(RuleTable rt)
        {

            RulesetDef rsd = new RulesetDef();
            for (int i = 0; i < rt.Rows.Count; i++)
            {
                RuleTable.Row row = rt.Rows[i];
                RuleDef rd = new RuleDef();
                List<string> acts = new List<string>();
                List<string> conds = new List<string>();
                for (int j = 0; j < rt.Columns.Count; j++)
                {
                    string v = row.Values[j].Trim();
                    RuleTable.Column col = rt.Columns[j];
                    if (col.Expression == "%_RuleId")
                    {
                        rd.Id = v;
                    }
                    else if (col.Expression == "%_RuleLabel")
                    {
                        rd.Label = v;
                    }
                    else
                    {
                        if (!string.IsNullOrEmpty(v))
                        {
                            if (col.Expression.StartsWith("%"))
                                acts.Add(BuildBooExpression(col.Expression.Substring(1), v, true));
                            else
                                conds.Add(BuildBooExpression(col.Expression, v, false));
                        }
                    }
                }
                rd.Actions = acts.ToArray();
                rd.Conditions = conds.ToArray();
                if (string.IsNullOrEmpty(rd.Id))
                    throw new Exception("Rule witout id in line " + row.LineNumber);
                if (rd.Conditions.Length == 0) throw new Exception("Rule without conditions: " + rd.Id);
                rsd.Rules.Add(rd);
            }
            
            return rsd;
        }

        public RulesetDef ReadRulesetFromCsv(string csvFile, Encoding enc)
        {
            RuleTable rt = ReadCsv(csvFile, enc);
            return BuildRuleset(rt);
        }

        public RulesetDef ReadRulesetFromCsv(CsvReader cr)
        {
            RuleTable rt = ReadCsv(cr);
            return BuildRuleset(rt);
        }
        
    }
}
